package com.cps.frameauth.common.aspect;

import cn.hutool.crypto.digest.MD5;
import com.cps.frameauth.common.annotation.AntiSpam;
import com.cps.framecommon.constant.NumberConstant;
import com.cps.framecommon.response.Response;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Objects;

/**
 * 方法拦截器：拦截标记AntiSpam的方法，校验用户是否重复发送相同的数据请求
 *
 * @author caopeisheng
 */
@Slf4j
@Aspect
@Component
public class AntiSpamAspect {

    private static final ObjectMapper MAPPER = new ObjectMapper();

    private static final String ANTI_SPAM_KEY = "HOBBY_ANTI_SPAM:";

    private static final String TOKEN = "Token";

    @Resource
    private RedisTemplate<String, Integer> redisTemplate;

    @Pointcut("@annotation(com.cps.frameauth.common.annotation.AntiSpam)")
    public void antiSpam() {
    }

    @Around("antiSpam()")
    public Object antiSpam(ProceedingJoinPoint point) throws Throwable {

        // 获取请求对象
        HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();

        // 获取注解信息
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        AntiSpam antiSpam = method.getAnnotation(AntiSpam.class);
        if (antiSpam != null) {
            // 从缓存中获取用户请求接口信息
            String key = createAntiSpamKey(request);
            Integer usageCount = redisTemplate.opsForValue().get(key);
            if (usageCount != null) {
                if ((usageCount + 1) > antiSpam.frequency()) {
                    return Response.failed("重复提交，稍后再试~");
                } else {
                    redisTemplate.opsForValue().increment(key);
                }
            } else {
                redisTemplate.opsForValue().set(key, NumberConstant.ONE, antiSpam.time(), antiSpam.timeUnit());
            }
        }

        return point.proceed();
    }

    /**
     * 生成key
     *
     * @return
     */
    private String createAntiSpamKey(HttpServletRequest request) throws IOException {

        // 获取请求参数
        Map<String, String[]> parameterMap = request.getParameterMap();

        // 获取用户token
        String token = request.getHeader(TOKEN);

        String keyStr = request.getRequestURI();

        if (parameterMap.isEmpty()) {
            // 请求体的数据只能读取一次，需要进行包装Request进行解决
            keyStr += IOUtils.toString(request.getInputStream(), "UTF-8");
        } else {
            keyStr += MAPPER.writeValueAsString(request.getParameterMap());
        }

        if (StringUtils.isNotBlank(token)) {
            keyStr += token;
        }

        return ANTI_SPAM_KEY + MD5.create().digestHex(keyStr);
    }

}
